package day28.ThreadTest;

/**
 * 死锁演示类
 * 展示多线程环境中可能出现的死锁情况
 * 
 * 死锁概念：死锁是指两个或多个线程在执行过程中，因争夺资源而造成的一种互相等待的现象，
 * 若无外力作用，它们都将无法推进下去。
 * 
 * 死锁产生的四个必要条件（Coffman条件）：
 * 1. 互斥条件：一个资源每次只能被一个线程使用
 * 2. 请求与保持条件：一个线程因请求资源而阻塞时，对已获得的资源保持不放
 * 3. 不可剥夺条件：线程已获得的资源在未使用完之前不能强行剥夺
 * 4. 循环等待条件：若干线程之间形成一种头尾相接的循环等待资源关系
 * 
 * 解决死锁的方法：
 * 1. 预防死锁：破坏四个必要条件中的一个或多个
 * 2. 避免死锁：如银行家算法
 * 3. 检测死锁：允许死锁发生，定期检测并解决
 * 4. 解除死锁：撤销或挂起一些进程，回收资源
 */
public class DeadlockDemo {
    public static void main(String[] args) {
        // 创建两个资源对象
        Resource resource1 = new Resource("Resource1");
        Resource resource2 = new Resource("Resource2");
        
        // 创建两个线程，分别以不同顺序获取锁 - 这是导致死锁的关键
        // 线程1先获取resource1锁，再尝试获取resource2锁
        Thread thread1 = new Thread(new Worker(resource1, resource2), "Thread-1");
        // 线程2先获取resource2锁，再尝试获取resource1锁
        Thread thread2 = new Thread(new Worker(resource2, resource1), "Thread-2");
        
        // 启动线程
        thread1.start();
        thread2.start();
        
        // 等待一段时间观察死锁现象
        // Thread.sleep()方法：使当前线程进入阻塞状态指定毫秒数
        // InterruptedException：当线程在睡眠时被其他线程中断会抛出此异常
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Main thread finished");
    }
}

/**
 * 资源类
 * 
 * synchronized关键字知识点：
 * 1. synchronized是Java中的内置锁机制
 * 2. 可以修饰实例方法、静态方法和代码块
 * 3. synchronized实例方法的锁是当前实例对象(this)
 * 4. 保证同一时刻只有一个线程可以执行同步代码块或方法
 * 5. 具有可见性和原子性保障
 */
class Resource {
    private String name;
    
    public Resource(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    /**
     * 模拟使用资源的方法
     * synchronized修饰实例方法，锁住的是当前Resource实例
     * 
     * 线程同步知识点：
     * 1. synchronized保证方法的原子性执行
     * 2. 进入方法前需要获取对象锁，退出方法时自动释放锁
     * 3. 其他线程试图访问该方法时会被阻塞，直到锁被释放
     */
    public synchronized void use() {
        System.out.println(Thread.currentThread().getName() + " is using " + name);
        try {
            // Thread.sleep()方法不会释放已持有的锁
            // sleep期间线程仍然持有对象锁，其他线程无法访问同步方法
            Thread.sleep(100); // 模拟资源使用时间
        } catch (InterruptedException e) {
            // 恢复中断状态，保持线程的中断语义
            Thread.currentThread().interrupt();
        }
    }
}

/**
 * 工人类，负责获取和使用资源
 * 
 * 多线程编程知识点：
 * 1. 实现Runnable接口是创建线程的方式之一
 * 2. Runnable方式比继承Thread类更灵活，有利于资源共享
 * 3. 线程同步块synchronized(object)可以指定任意对象作为锁
 */
class Worker implements Runnable {
    private Resource resource1;
    private Resource resource2;
    
    public Worker(Resource resource1, Resource resource2) {
        this.resource1 = resource1;
        this.resource2 = resource2;
    }
    
    @Override
    public void run() {
        // 尝试获取两个资源的锁 - 嵌套锁获取是死锁的常见原因
        // synchronized同步块知识点：
        // 1. synchronized(resource1)表示以resource1对象作为锁
        // 2. 进入同步块前必须获取resource1对象的内部锁
        // 3. 同步块结束时自动释放锁
        synchronized (resource1) {
            System.out.println(Thread.currentThread().getName() + " acquired lock on " + resource1.getName());
            
            // 模拟一些处理时间
            // 这个延迟增加了发生死锁的概率
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            
            // 尝试获取第二个资源的锁
            // 死锁发生点：线程1持有resource1锁等待resource2锁
            //           线程2持有resource2锁等待resource1锁
            synchronized (resource2) {
                System.out.println(Thread.currentThread().getName() + " acquired lock on " + resource2.getName());
                resource1.use();
                resource2.use();
            }
        }
        
        System.out.println(Thread.currentThread().getName() + " finished work");
    }
}